package tutor;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.util.glu.GLU.*;
import static org.lwjgl.input.Keyboard.*;
import static org.lwjgl.input.Mouse.*;

import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.util.HashSet;
import java.util.Set;

import org.lwjgl.BufferUtils;
import org.lwjgl.input.Keyboard;
//import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Matrix3f;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;

public class Tutorial9 extends Game {
    
    ShaderProgram shaderProgram;
    
    Camera camera;
    Camera camera2;
    
    Wormhole wormhole;
    
    Set<Model> planes;
    
    /**
     * Initialize resources
     */
    public void init() {
    	System.out.println("note: maximum clipping planes number is " + GL11.glGetInteger(GL_MAX_CLIP_PLANES));
    	//READ: http://stackoverflow.com/questions/16886335/opengl-clipping-in-3d-space
    	
    	Display.setTitle("3d test 5");
    	
    	camera = new Camera(45, (float) Display.getWidth() / (float) Display.getHeight(), 0.1f, 100f);
    	camera.setPosition(new Vector3f(6, 0, 8));
    	camera.setRotation(0, 135, 0);
    	
    	camera2 = new Camera(45, (float) Display.getWidth() / (float) Display.getHeight(), 10f, 100f);
    	camera2.setPosition(new Vector3f(6, 0, 8));
    	camera2.setRotation(0, 135, 0);
    	
    	planes = new HashSet<Model>();
//    	planes.add(Model.loadOBJModel("tutor/AirHawk.obj", 0, 0, 0));
//    	planes.add(Model.loadOBJModel("tutor/AirHawk.obj", 25, 0, 0));
    	for(int i = 0; i < 5; i++) {
    		for(int j = 0; j < 5; j++) {
    			for(int k = 0; k < 5; k++) {
    				planes.add(Model.loadOBJModel("tutor/AirHawk.obj", i, j, k));
    			}
    		}
    	}
    	
//    	//no rotation
//    	wormhole = new Wormhole(new Vector3f(0, 0, 0), new Vector3f(25, 0, 0));
    	
    	//with rotation
    	wormhole = new Wormhole(new Vector3f(0, 0, 0), new Vector3f(25, 0, 0), new Vector3f(0, 45, 0), new Vector3f(0, 45, 0));
        
        // Create shader program
        shaderProgram = new ShaderProgram();
        shaderProgram.attachVertexShader("tutor/shader.vert");
        shaderProgram.attachFragmentShader("tutor/shader.frag");
        shaderProgram.link();
        
        glCullFace(GL_FRONT_FACE);
    }
       
    
    int dx = 0;
    int dy = 0;
    int maxDX = 0;
    int maxDY = 0;
    boolean isAllowMouse = false;
    
    /**
     * Update logic
     */
    public void update(long elapsedTime) {
        if (isKeyDown(KEY_ESCAPE))
            end();
        
        if (isKeyDown(KEY_Q)) {
        	isAllowMouse = true;
        	dx = 0;
        	dy = 0;
        }
        
        if(isAllowMouse) {
            dx = getDX();
            dy = getDY();
        }
        
        /*set boundaries*/
        if(dx > 100 || dx < -100)
        	dx = 0;
        if(dy > 100 || dy < -100)
        	dy = 0;
        
        
        // Look up/down
        camera.addRotation(-dy, 0, 0);
        // Turn left/right
        camera.addRotation(0, dx, 0);

        
        // Move front
        if (isKeyDown(KEY_W))
            camera.move(0.1f, 1);

        // Move back
        if (isKeyDown(KEY_S))
            camera.move(-0.1f, 1);

        // Strafe left
        if (isKeyDown(KEY_A))
            camera.move(0.1f, 0);

        // Strafe right
        if (isKeyDown(KEY_D))
            camera.move(-0.1f, 0);
        
        // Up
        if (isKeyDown(KEY_SPACE))
            camera.moveVertical(0.1f, -1);
        
        // Down
        if (isKeyDown(KEY_Z))
            camera.moveVertical(0.1f, 1);
        
        
        float cosFi = (float) Math.cos((float) Vector3f.angle(wormhole.entryRot.normalise(null), wormhole.exitRot.normalise(null)));
        float sinFi = (float) Math.sin((float) Vector3f.angle(wormhole.entryRot.normalise(null), wormhole.exitRot.normalise(null)));
        
        Matrix3f m = new Matrix3f();
        m.setZero();
        m.m11 = 1;
//        m.m33 = 1;
        m.m00 = cosFi;
        m.m02 = sinFi;
        m.m20 = -sinFi;
        m.m22 = cosFi;
        Vector3f camPos = camera.getPosition();
        Vector3f wormPos = wormhole.entry;
        Vector3f corrVect = Vector3f.sub(camPos, wormPos, null);
        Vector3f res = Matrix3f.transform(m, corrVect, null);
        Vector3f newPos = Vector3f.add(wormhole.exit, res, null);
        
        Vector3f newRot = Vector3f.add(camera.getRotarion(), wormhole.getRotDiff(), null);
        
        camera2.setPosition(newPos);
        camera2.setRotation(newRot);
    }
    
    /**
     * Render to the screen
     */
	public void render() {
		// Clear the screen
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		// Perform camera transformations
		camera2.apply();
		camera.apply();

		// Bind the shaders
		shaderProgram.bind();
		
		shaderProgram.setUniform("projection", camera2.getProjectionMatrix());
		shaderProgram.setUniform("view", camera2.getViewMatrix());
		shaderProgram.setUniform("lightPos", camera2.getPosition());
		shaderProgram.setUniform("mNormal", camera2.getNormalMatrix());
		
		glEnable(GL_SCISSOR_TEST);
		for(Model plane : planes) {
			plane.render();
		}
		glDisable(GL_SCISSOR_TEST);
		
		glClear(GL_DEPTH_BUFFER_BIT); //primary camera renders everything next on top
		
		shaderProgram.setUniform("projection", camera.getProjectionMatrix());
		shaderProgram.setUniform("view", camera.getViewMatrix());
		shaderProgram.setUniform("lightPos", camera.getPosition());
		shaderProgram.setUniform("mNormal", camera.getNormalMatrix());
		
		
		for(Model plane : planes) {
			plane.render();
		}
		
		// Unbind the shaders
		ShaderProgram.unbind();
	}
    
    /**
     * Display resized
     */
	public void resized() {
		glViewport(0, 0, Display.getWidth(), Display.getHeight());
	}

	/**
	 * Dispose resources
	 */
	public void dispose() {
		for(Model plane : planes) {
			plane.dispose();
		}
		shaderProgram.dispose();
	}

	public static void main(String[] args) {
		new Tutorial9();
	}
}
